Option Explicit On 
Option Strict On

Imports TreeCreeper01Interfaces

Friend Class cTaxonomicTree

    Implements ITaxonomicDistanceProvider
    Implements ITaxonomicTree

    Private mRootNode As cTaxonomicTreeNode
    Private mAllSpecies As cTaxonomicSpeciesCollection

    Private mTreeTraversalTaxonomicDistances As cTaxonomicDistanceLookupTable
    Private mDoUseOverride As Boolean
    Private mOverrideTaxonomicDistances As cTaxonomicDistanceLookupTable

    Private mCurrentMaximumShortLeafNodeId As Integer

    Public Sub New()

        mRootNode = New cTaxonomicTreeNode(Nothing, "ROOT NODE", "ROOT NODE", 0)

    End Sub

    Private ReadOnly Property RootNode() As ITaxonomicTreeNode Implements ITaxonomicTree.RootNode
        Get
            Return mRootNode
        End Get
    End Property

    Public Function CreateOverrideDistanceLookupTable() As ITaxonomicDistanceLookupTable Implements ITaxonomicTree.CreateOverrideDistanceLookupTable

        mOverrideTaxonomicDistances = New cTaxonomicDistanceLookupTable(AllSpecies)

        mDoUseOverride = True
        Return mOverrideTaxonomicDistances

    End Function

    Private ReadOnly Property AllSpecies() As ITaxonomicSpeciesCollection Implements ITaxonomicTree.AllSpecies
        Get
            If mAllSpecies Is Nothing Then
                InitializeAndCacheLeafNodeCollection()
            End If
            Return mAllSpecies
        End Get
    End Property

    Public Function TaxonomicDistanceBetweenNodes(ByVal FirstNode As ITaxonomicTreeNode, ByVal SecondNode As ITaxonomicTreeNode) As Single Implements ITaxonomicTree.TaxonomicDistanceBetweenNodes

        Return TaxonomicDistanceBetweenSpecies(SpeciesForNode(FirstNode), SpeciesForNode(SecondNode))

    End Function

    Public Function LogicalDistanceBetweenNodes(ByVal FirstNode As ITaxonomicTreeNode, ByVal SecondNode As ITaxonomicTreeNode) As Integer Implements ITaxonomicTree.LogicalDistanceBetweenNodes

        'Go direct to the data object
        Return CInt(mTreeTraversalTaxonomicDistances.TaxonomicDistanceBetweenSpecies(SpeciesForNode(FirstNode), SpeciesForNode(SecondNode)))

    End Function

    Private Function TaxonomicDistanceBetweenSpecies(ByVal FirstSpecies As ITaxonomicSpecies, ByVal SecondSpecies As ITaxonomicSpecies) As Single Implements ITaxonomicDistanceProvider.TaxonomicDistanceBetweenSpecies

        If mDoUseOverride Then
            Return mOverrideTaxonomicDistances.TaxonomicDistanceBetweenSpecies(FirstSpecies, SecondSpecies)
        Else
            Return mTreeTraversalTaxonomicDistances.TaxonomicDistanceBetweenSpecies(FirstSpecies, SecondSpecies)
        End If

    End Function

    Private Function SpeciesForNode(ByVal Node As ITaxonomicTreeNode) As cTaxonomicSpecies

        Dim NodeIntimate As cTaxonomicTreeNode
        NodeIntimate = TryCast(Node, cTaxonomicTreeNode)

        If Not NodeIntimate Is Nothing Then
            Return NodeIntimate.Species
        Else
            Return Nothing
        End If

    End Function

    Private Sub InitializeAndCacheLeafNodeCollection()

        Dim LeafNodeList As List(Of cTaxonomicTreeNode)

        LeafNodeList = New List(Of cTaxonomicTreeNode)
        mAllSpecies = New cTaxonomicSpeciesCollection
        RecursivelyInvestigateNodeForAdditionIntoLeafNodeCollection(mRootNode, LeafNodeList)
        CalculateAndCacheAllTaxonomicDistances(LeafNodeList)

    End Sub

    Private Sub RecursivelyInvestigateNodeForAdditionIntoLeafNodeCollection(ByVal Node As cTaxonomicTreeNode, _
        ByVal RecipientList As List(Of cTaxonomicTreeNode))

        Dim NodeIndex As Integer
        Dim ChildNode As cTaxonomicTreeNode
        Dim Species As cTaxonomicSpecies
        Dim SpeciesName As String

        If Node.ChildNodes.Count = 0 Then
            'It's a leaf node of the tree: a species node.
            With Node
                SpeciesName = .ParentNode.Name
                SpeciesName = SpeciesName & "_" & .Name
                Species = New cTaxonomicSpecies(.UniqueKey, SpeciesName)
            End With
            mAllSpecies.AddSpecies(Species)
            Node.Species = Species
            RecipientList.Add(Node)
        Else
            'Is non-leaf node, repeat procedure for each
            For NodeIndex = 1 To Node.ChildNodes.Count
                ChildNode = Node.ChildNodes.Item(NodeIndex - 1)
                RecursivelyInvestigateNodeForAdditionIntoLeafNodeCollection(ChildNode, RecipientList)
            Next
        End If

    End Sub

    Private Sub CalculateAndCacheAllTaxonomicDistances(ByVal LeafNodeList As List(Of cTaxonomicTreeNode))

        Dim LeafNodeCount As Integer
        Dim StartNodeIndex As Integer
        Dim StartNode As cTaxonomicTreeNode
        Dim StartSpecies As ITaxonomicSpecies
        Dim EndNodeIndex As Integer
        Dim EndNode As cTaxonomicTreeNode
        Dim EndSpecies As ITaxonomicSpecies
        Dim TaxonomicDistance As Integer

        LeafNodeCount = LeafNodeList.Count
        mTreeTraversalTaxonomicDistances = New cTaxonomicDistanceLookupTable(mAllSpecies)

        For StartNodeIndex = 1 To LeafNodeCount
            'Debug.WriteLine("Calculating taxonomic distances originating from node " & StartNodeIndex & " / " & LeafNodeCount)

            StartNode = LeafNodeList.Item(StartNodeIndex - 1)
            StartSpecies = StartNode.Species

            'Half the matrix is redundant, but the object takes care writing double
            'This loop includes self-comparison to set a value for each place in the matrix (even if it's zero)-

            For EndNodeIndex = StartNodeIndex To LeafNodeCount
                EndNode = LeafNodeList.Item(EndNodeIndex - 1)
                EndSpecies = EndNode.Species

                ' check that these are all actually leaf nodes
                If StartNode.DepthFromRootNode <> EndNode.DepthFromRootNode Then
                    Throw New Exception("Given nodes (" & StartNode.Name & " / " & EndNode.Name & ") do not have same distance from the root node. Maybe one or both of them are non-leaf nodes?")
                End If

                'Calculate the distance and insert to the cache array
                TaxonomicDistance = CalculateSingleTaxonomicDistance(StartNode, EndNode)
                mTreeTraversalTaxonomicDistances.TaxonomicDistanceBetweenSpecies(StartSpecies, EndSpecies) = TaxonomicDistance
            Next
        Next

    End Sub

    Private Function CalculateSingleTaxonomicDistance(ByVal StartNode As ITaxonomicTreeNode, ByVal EndNode As ITaxonomicTreeNode) As Integer

        Dim TaxonomicDistance As Integer

        Do While Not StartNode Is EndNode
            'Note that in case of root node we should NEVER arrive here due to do..while check above
            'Check that going up is possible
            If StartNode.ParentNode Is Nothing OrElse EndNode.ParentNode Is Nothing Then
                Throw New Exception("Given leaf nodes are not part of the same tree? (!)")
            End If
            'Go up one level
            StartNode = StartNode.ParentNode
            EndNode = EndNode.ParentNode
            'Add two steps to taxonomic distance
            TaxonomicDistance += 2
        Loop

        Return TaxonomicDistance

    End Function


End Class
